package mushystuff.dtb.core.service.to;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import mushystuff.dtb.core.model.PersistableBean;
import ognl.Ognl;
import ognl.OgnlException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

class OGNLAssemblerService implements AssemblerService {

    private Log log = LogFactory.getLog(OGNLAssemblerService.class);

    /*
     * (non-Javadoc)
     * 
     * @see mushystuff.dtb.service.to.AssemblerService#fromTO(java.lang.Class,
     * mushystuff.dtb.service.to.TransferObject)
     */
    public <B extends PersistableBean> B fromTO(Class<B> toClass, TransferObject to)
	    throws AssemblerServiceException {
	OGNLAssociation association = to.getClass().getAnnotation(OGNLAssociation.class);
	if (association == null || !association.value().equalsIgnoreCase(toClass.getSimpleName())) {
	    throw new IllegalArgumentException("toClass must match class in to's OGNLAssociation");
	}
	// Create bean
	B bean;
	try {
	    bean = toClass.newInstance();
	} catch (InstantiationException e) {
	    throw new AssemblerServiceException("Could not create PersistableBean " + toClass, e);
	} catch (IllegalAccessException e) {
	    throw new AssemblerServiceException("Could not create PersistableBean " + toClass, e);
	}
	// Set field values of bean
	setBeanFields(to.getClass().getDeclaredFields(), to, bean);
	Class<?> superClass = to.getClass().getSuperclass();
	while (superClass != null) {
		setBeanFields(superClass.getDeclaredFields(), to, bean);
		superClass = superClass.getSuperclass();
	}
	return bean;
    }

    /*
     * (non-Javadoc)
     * 
     * @see mushystuff.dtb.service.to.AssemblerService#toTO(java.lang.Class,
     * mushystuff.dtb.core.model.PersistableBean)
     */
    public <T extends TransferObject> T toTO(Class<T> toClass, PersistableBean bean)
	    throws AssemblerServiceException {
	OGNLAssociation association = toClass.getAnnotation(OGNLAssociation.class);
	if (association == null || !association.value().equalsIgnoreCase(bean.getClass().getSimpleName())) {
	    throw new IllegalArgumentException("PersistableBean must match class in toClass's OGNLAssociation");
	}
	// Create TransferObject
	T to;
	try {
	    to = toClass.newInstance();
	} catch (InstantiationException e) {
	    throw new AssemblerServiceException("Could not create TransferObject " + toClass, e);
	} catch (IllegalAccessException e) {
	    throw new AssemblerServiceException("Could not create TransferObject " + toClass, e);
	}
	// Set field values in TransferObject
	setTOFields(toClass.getDeclaredFields(), to, bean);
	Class<?> superClass = toClass.getSuperclass();
	while (superClass != null) {
		setTOFields(superClass.getDeclaredFields(), to, bean);
		superClass = superClass.getSuperclass();
	}
	return to;
    }

    private <T extends TransferObject> void setBeanFields(Field[] fields, T to, PersistableBean bean) {
    	for (Field field : fields) {
    	    OGNLExpression expression = field.getAnnotation(OGNLExpression.class);
    	    if (expression == null || expression.setable() == false) {
    		log.info("Annotation not found for " + field.getName());
    		continue;
    	    }
    	    field.setAccessible(true);
    	    try {
    		Ognl.setValue(expression.value(), bean, field.get(to));
    	    } catch (IllegalArgumentException e) {
    		log.warn("Could not find field " + field + " in bean " + bean.getClass(), e);
    	    } catch (IllegalAccessException e) {
    		log.warn("Could not access field " + field + " in bean " + bean.getClass(), e);
    	    } catch (OgnlException e) {
    		log.warn("Could not set field " + field + " in bean " + bean.getClass(), e);
    	    }
    	}
    }
    
    private <T extends TransferObject> void setTOFields(Field[] fields, T to, PersistableBean bean) {
    	// Set field values in TransferObject
    	for (Field field : fields) {
    	    OGNLExpression expression = field.getAnnotation(OGNLExpression.class);
    	    if (expression == null) {
    		log.info("Annotation not found for " + field.getName());
    		continue;
    	    }
    	    log.info("setting " + field.getName());
    	    field.setAccessible(true);
    	    try {
    		field.set(to, Ognl.getValue(expression.value(), bean));
    	    } catch (IllegalArgumentException e) {
    		log.warn("Could not find field " + expression.value() + " in bean " + bean.getClass(), e);
    	    } catch (IllegalAccessException e) {
    		log.warn("Could not access field " + expression.value() + " in bean " + bean.getClass(), e);
    	    } catch (OgnlException e) {
    		log.warn("Could not set field " + expression.value() + " in bean " + bean.getClass(), e);
    	    }
    	}
    }
    
    /*
     * (non-Javadoc)
     * 
     * @see
     * mushystuff.dtb.service.to.AssemblerService#toTOArray(java.lang.Class,
     * java.util.Collection)
     */
    @SuppressWarnings("unchecked")
    public <T extends TransferObject> T[] toTOArray(Class<T> toClass,
	    Collection<? extends PersistableBean> beans) throws AssemblerServiceException {
	T[] res = (T[])Array.newInstance(toClass, beans.size());
	Iterator<? extends PersistableBean> beansIterator = beans.iterator();
	for (int i = 0; i < beans.size(); i++) {
	    res[i] = toTO(toClass, beansIterator.next());
	}
	return res;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * mushystuff.dtb.service.to.AssemblerService#fromTOCollection(java.lang
     * .Class, mushystuff.dtb.service.to.TransferObject[])
     */
    public <B extends PersistableBean> Collection<B> fromTOCollection(Class<B> toClass, TransferObject[] tos)
	    throws AssemblerServiceException {
	Collection<B> res = new ArrayList<B>();
	for (TransferObject to : tos) {
	    res.add(fromTO(toClass, to));
	}
	return res;
    }
}
